package cn.com.cym.gamezao.base;

import cn.com.cym.gamezao.exception.BaseException;
import cn.com.cym.gamezao.jwt.RequestUser;
import cn.com.cym.gamezao.util.CommonUtils;
import cn.com.cym.gamezao.util.GenericSuperclassUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ReflectUtil;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public abstract class BaseImpl<T extends BaseEntity> implements BaseService<T>{

    Logger logger = LoggerFactory.getLogger(getClass());

    public abstract BaseRepository<T,Long> getJpaRepository();

    @Override
    public T fetchOne(Long id) {
        return getJpaRepository().getById(id);
    }

    /**
     * 分页查询
     * @param searchPage pageRequest
     * @return  Page
     */
    @Override
    public abstract Page<T> findByPageable(SearchPage searchPage);


    @Override
    public Page<T> findByPageableDemo(SearchPage searchPage){
        String[] sortBy = searchPage.getSortBy().toArray(new String[searchPage.getSortBy().size()]);

        Pageable pageable = PageRequest.of(searchPage.getCurrent()-1, searchPage.getPageSize(), searchPage.getSort(), sortBy);

        Specification<T> specification = new Specification<>() {

            private static final long serialVersionUID = -2661077778894635593L;

            /**
             * @param root: 代表查询的实体类.
             * @param query: 可以从中可到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以
             * 来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
             * @param  cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
             * @return  Predicate 类型, 代表一个查询条件.
             */
            @SneakyThrows
            @Override
            public Predicate toPredicate(Root<T> root,
                                         CriteriaQuery<?> query, CriteriaBuilder cb) {
                //final Type genericSuperclass = this.getClass().getGenericSuperclass();
                Class<?> actualTypeArgument = GenericSuperclassUtil.getActualTypeArgument(BaseImpl.class);
                final Map<String, Field> fieldMap = ReflectUtil.getFieldMap(actualTypeArgument);
                return cb.and(CommonUtils.pageSet(fieldMap,searchPage,cb,root));
            }
        };
        return this.getJpaRepository().findAll(specification, pageable);
    };

    @Override
    public T save(T t) {
        logger.trace("save entity");
        if(t.getId()==null){
            t.setCreateTime(LocalDateTime.now());
            t.setCreateUserId(RequestUser.getOne()==null?-1L:RequestUser.getOne().getId());
        }else{
            T t1 = fetchOne(t.getId());
            t.setCreateTime(t1.getCreateTime());
            t.setCreateUserId(t1.getCreateUserId());
            t.setVersionCode(t1.getVersionCode()+1);
        }
        t.setModifyTime(LocalDateTime.now());
        t.setModifyUserId(RequestUser.getOne()==null?-1L:RequestUser.getOne().getId());
        return getJpaRepository().save(t);
    }

    @Override
    public T update(T t, Long id) {
        logger.trace("update entity");
        final T storeT = getJpaRepository().getById(id);
        if(storeT==null){
            throw  new BaseException("无法获取到数据，更新失败",new ResultResponse("无法获取到数据，更新失败", HttpStatus.BAD_REQUEST.value()));
        }
        logger.info("id:"+storeT.getId());
       /* t.setModifyTime(LocalDateTime.now());
        t.setModifyUserId(RequestUser.get()==null?-1L:RequestUser.get().getId());
        t.setVersionCode(storeT.getVersionCode()+1);*/
        //为null值不做处理（判断如mybatis写法）
        BeanUtil.copyProperties(t,storeT, new CopyOptions(t.getClass(),true));
        return this.save(storeT);
    }

    @Override
    public int physicalDelete(Long id) {
        logger.trace("physical delete entity ");
        getJpaRepository().deleteById(id);
        return 1;
    }

    @Override
    public int physicalDeleteByIds(List<Long> ids) {
        for(Long id:ids){
            this.physicalDelete(id);
        }
        return ids.size();
    }

    @Override
    public int logicDelete(Long id) {

        logger.trace("logic delete entity");
        final T t = fetchOne(id);
        if(t==null){
            return 0;
        }
        t.setDeleted(true);
        save(t);
        return 1;
    }

    @Override
    public Optional<T> fetchById(Long id) {
        logger.trace("fetch entity by id");
        return getJpaRepository().findById(id);
    }

    @Override
    public List<T> getAll() {
        logger.trace("get all entity");
        return getJpaRepository().findAll();
    }

    @Override
    public List<T> getAllVisible(){
        return this.getJpaRepository().findAllByDeleted(false);
    }


    @Override
    public int deleteIds(List<Long> ids, boolean logic) {
        logger.trace("delete  entity  in ids ");
        int i=0;
        if(logic){
            for(long id:ids){
                final int i1 = this.logicDelete(id);
                i=i+i1;
            }
        }else{
            for(long id:ids){
                final int i1 = this.physicalDelete(id);
                i=i+i1;
            }
        }
        return i;
    }
}
